Skip to content

Go Web编程

======【Go与Web应用】======

使用Go语言构建Web应用

  • 初始化模块文件 go.mod
go
go mod init go-web-programming
  • 创建第一个 Go Web 应用 main.go
go
package main

import (
	"fmt"
	"net/http"
)

func handler(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprintf(writer, "Hello World, %s!", request.URL.Path[1:])
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
  • 编译并安装
go
go install go-web-programming
  • 运行 Go Web 服务
go
cd $GOPATH/bin
go-web-programming

如果PATH环境变量没配,需要 export PATH=$PATH:$GOPATH/bin,然后运行 source ~/.bash_profilesource ~/.zshrc 来使更改生效。

  • 访问URL

浏览器打开:localhost:8080/zhangsan

输出:Hello World, zhangsan!

ChitChat论坛

在 Linux 或 FreeBSD 系统上安装

shell
sudo apt-get install postgresql postgresql-contrib

在 Windows 系统上安装

安装 EnterpriseDB 软件

连接数据库

shell
createdb chitchat
  • 初始化数据库
shell
psql -f setup.sql -d chitchat

======【Web应用的基本组成部分】======

接收请求

GO语言拥有一系列成熟的标准库,如net/httphtml/template,这些标准库可以用于构建Web应用。

  • Go的net/http标准库

  • ClientResponseHeaderRequestCookie对客户端进行支持;

  • ServerServerMuxHandler/HandleFuncResponseWriterHeaderRequestCookie则对服务器进行支持。

  • 串联多个处理器和处理器函数

就像搭积木一样,既然我们可以串联起两个函数,那么自然也可以串联起更多函数。串联多个函数可以让程序执行更多动作,这种做法有时候也称为 管道处理(pipeline processing)

  • ServeMux和DefaultServeMux

ServeMux是一个HTTP请求多路复用器,它负责接收HTTP请求并根据请求中的URL将请求重定向到正确的处理器。

ServeMux的一个缺陷是无法使用变量实现URL模式匹配。

最小惊讶原则

最小惊讶原则,也称最小意外原则,是设计包括软件在内的一切事物的一条通用规则,它指的是我们在进行设计的时候,应该做那些合乎常理的事情,使事物的行为总是显而易见、始终如一并且合乎情理。

使用HTTP/2

在1.6或以上版本,如果使用HTTPS模式启动服务器,那么服务器将默认使用HTTP/2

处理请求Request和ResponseWriter

请求和响应

HTTP报文是在客户端和服务器之间传递的消息,它分为HTTP请求和HTTP响应两种类型。

这两种类型的报文都拥有相同的结构:

(1) 请求行或者响应行 (2) 零个或多个首部 (3) 一个空行 (4) 一个可选的报文主体

如:

text
GET /Protocols/rfc2616.html HTTP/1.1
Host: www.w3.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
(empty line)

Request结构

Request结构主要由以下部分组成:

  • URL字段
  • Header字段
  • Body字段
  • Form字段、PostForm字段和MultipartForm字段

请求URL

URL的一般格式为:

text
scheme://[userinfo@]host/path[?query][#fragment]

请求首部

go
h := r.Header

h := r.Heaer["Accept-Encoding"]

h := r.Header.Get("Accept-Encoding")
// 返回字符串形式的首部值,多个首部值将使用逗号分割。

PostForm字段

PostForm字段只支持application/x-www-form-urlencoded编码

MultipartForm字段

go
func getCookie(w http.ResponseWriter, r *http.Request) {
	r.ParseMultipartForm(1024)
	fmt.Fprintln(w, r.MultipartForm)
}
  • FormValue方法允许直接访问与给定键相关联的值

Form字段的唯一区别在于:因为FormValue方法在需要时会自动调用ParseForm方法或者ParseMultipartForm方法,所以用户在执行 FormValue方法之前,不需要手动调用上面提到的两个语法分析方法

go
r.FormValue("hello")

r.PostFormValue("hello")

r.PostForm("hello")

文件

multipart/form-data编码通常用于实现文件上传功能

go
file, _, err := r.FormFile("file")
if err == nil {
	data, err := ioutil.ReadAll(file)
	if err == nil {
		fmt.Fprintf(w, string(data))
	}
}

ResponseWriter

ResponseWriter看上去像是一个值,但它实际上却是一个带有结构指针的接口

ResponseWriter接口拥有以下3个方法:

  • Write
  • WriteHeader
  • Header

对ResponseWriter进行写入

go
str := `<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>
`
w.Write([]byte(str))
  • WriteHeader方法接受一个代表HTTP响应状态码的整数作为参数,并将这个整数用作HTTP响应的返回状态码;
go
w.WriteHeader(501)
  • 通过编写首部实现客户端重定向
go
// 顺序不能调换
w.Header().Set("Location", "https://www.xxx.com")
w.WriteHeader(302)
  • 编写JSON输出
go
w.Header().Set("Content-Type", "application/json")
post := &Post{
	User:    "Sau Sheong",
	Threads: []string{"first", "second", "third"},
}
json, _ := json.Marshal(post)
w.Write(json)
  • 将cookie发送至浏览器
go
c1 := http.Cookie{
	Name:     "c1",
	Value:    "value1",
	HttpOnly: true,
}
c2 := http.Cookie{
	Name:     "c2",
	Value:    "value2",
	HttpOnly: true,
}
w.Header().Set("Set-Cookie", c1.String())
w.Header().Add("Set-Cookie", c2.String())

// 如下等同,更简洁的写法
http.SetCookie(w, &c1)
http.SetCookie(w, &c2)
  • 从请求的首部获取cookie
go
func getCookie(w http.ResponseWriter, r *http.Request) {
	h := r.Header["Cookie"]
	fmt.Println(h)	// 返回一个切片[c1=value1; c2=value2]
}

// main函数绑定
http.HandleFunc("/getCookie", getCookie)
  • 使用cookie方法
go
c1, err := r.Cookie("c1")
if err != nil {
	fmt.Fprintln(w, "Cookie not found")
}
fmt.Println(c1)
  • 使用cookies方法
go
cs := r.Cookies()
fmt.Fprintln(w, cs)
// 返回一个切片[c1=value1 c2=value2]

内容展示template

模板引擎

Go的标准库通过text/templatehtml/template这两个库为模板提供了强有力的支持。

模板中的动作默认使用两个大括号包围。也可以自行指定其他定界符。

go
t, _ := template.ParseFiles("templates/index.html")
t.Execute(w, nil)

对模板进行语法分析

ParseGlob会对匹配给定模式的所有文件进行语法分析。

如果目录里面只有tmpl.html一个HTML文件,那么语句

go
t, _ := template.ParseFiles("tmpl.html")

和语句

go
t, _ := template.ParseGlob("*.html")

将产生相同的效果。

  • 手动处理模板错误
go
t := template.Must(template.ParseFiles("tmpl.html"))

Must函数可以包裹起一个函数,被包裹的函数会返回一个指向模板的指针和一个错误,如果这个错误不是nil,那么Must函数将产生一个 panic

执行模板

  • 执行多个模板
go
t := template.ParseFiles("t1.html", "t2.html")
  • 指定模板传递变量
go
t, _ := template.ParseFiles("t1.html", "t2.html")
t.ExecuteTemplate(w, "t2.html", "Hello World")

动作

Go模板的动作就是一些嵌入在模板里面的命令,这些命令在模板中使用两个大括号进行包围。

几种主要的动作:

  • 条件动作
  • 迭代动作
  • 设置动作
  • 包含动作

条件动作

  • if条件
go
{{ if arg }}
    some content
{{ end }}
  • if-else条件
go
{{ if arg }}
    some content
{{ else }}
    other content
{{ end }}

迭代动作

迭代动作可以对数组、切片、映射或者通道进行迭代,而在迭代循环的内部,点(.)则会被设置为当前被迭代的元素。

go
{{ range array }}
    item {{ . }}
{{ end }}

// 范例2
{{ range . }}
    <li>{{ . }}</li>
{{ end }}

设置动作

设置动作允许用户在指定的范围之内为点(.)设置值。

text
{{ with arg }}
    Dot is set to arg
{{ end }}

// 带else
{{ with arg }}
    Dot is set to arg
{{ else }}
	Fallback if arg is empty
{{ end }}

介于{ { with arg } }{ { end } }之间的点将被设置为参数arg的值。

  • 在设置点的时候提供备选方案
go
// go代码
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, "hello")

// tmpl.html代码
<p>is hello {{ . }}</p>
{{ with "" }}
    <p>set to {{ . }}</p>
{{ else }}
    <p>still hello {{ . }}</p>
{{ end }}
<p>still hello {{ . }}</p>
// 输出:
// is hello hello
// still hello hello
// still hello hello

包含动作

包含动作允许用户在一个模板里面包含另一个模板,从而构建出嵌套的模板。

包含动作的格式为{ { template "name" } },其中name参数为被包含模板的名字。

go
{{ template "name" arg }}	// arg就是用户想要传递给被嵌套模板的数据

// 比如将t1的点传递给t2
{{ template "t2.html" . }}

参数、变量和管道

一个参数就是模板中的一个值

除了参数之外,用户还可以在动作中设置变量。变量以美元符号($)开头,就像这样:

go
$variable := value

模板中的管道(pipeline)是多个有序地串联起来的参数、函数和方法。比如:

go
{{ p1 | p2 | p3 }}

这里的p1、p2和p3可以是参数或者函数。管道允许用户将一个参数的输出传递给下一个参数,而各个参数之间则使用|分割。

go
{{ 12.3456 | printf "%.2f" }}

函数

Go的模板引擎函数都是受限制的:尽管这些函数可以接受任意多个参数作为输入,但它们只能返回一个值,或者返回一个值和一个错误。

最近更新